import {
  CommunicationWay,
  ServiceHandler,
  ServiceRegister,
} from "@main/shared/reflect";
import { TYPES } from "@main/shared/types";
import logger from "@shared/logger/main-logger";
import type {
  CreateMessageData,
  Message,
  UpdateMessageData,
} from "@shared/triplit/types";
import { inject, injectable } from "inversify";
import { EventNames, emitter, sendToThread } from "./event-service";
import type { MessageService } from "./message-service";

/**
 * ChatService handles AI conversation management and streaming status updates.
 *
 * This service orchestrates the chat flow by:
 * - Managing assistant message creation with proper ordering
 * - Setting up event listeners for provider conversation lifecycle
 * - Broadcasting real-time status updates to the renderer process
 *
 * The service acts as a bridge between provider events and UI updates,
 * ensuring consistent chat status across all active threads.
 */
@ServiceRegister(TYPES.ChatService)
@injectable()
export class ChatService {
  private threadIdTolastAssistantMessageMap = new Map<string, Message>();

  constructor(
    @inject(TYPES.MessageService) private messageService: MessageService,
  ) {
    this.setupConversationEventListeners();
  }

  private handleStreamProgress(threadId: string, delta?: string) {
    if (delta) {
      const lastAssistantMessage =
        this.threadIdTolastAssistantMessageMap.get(threadId);
      if (lastAssistantMessage) {
        this.threadIdTolastAssistantMessageMap.set(threadId, {
          ...lastAssistantMessage,
          content: lastAssistantMessage.content + delta,
        });
      }
    }

    sendToThread(threadId, EventNames.CHAT_STREAM_STATUS_UPDATE, {
      threadId,
      delta,
      status: "pending",
    });
  }

  private handleThreadDeleted(threadId: string) {
    if (this.threadIdTolastAssistantMessageMap.has(threadId)) {
      this.threadIdTolastAssistantMessageMap.delete(threadId);
    }
  }

  private async handleThreadStreamStatusUpdate(
    threadId: string,
    status: "success" | "error" | "stop" | "initializing",
  ) {
    if (["success", "error", "stop"].includes(status)) {
      const lastAssistantMessage =
        this.threadIdTolastAssistantMessageMap.get(threadId);
      if (lastAssistantMessage) {
        await this.updateMessage(lastAssistantMessage.id, {
          content: lastAssistantMessage.content,
        });
      }
    }

    sendToThread(threadId, EventNames.CHAT_STREAM_STATUS_UPDATE, {
      threadId,
      status,
    });
  }

  /**
   * Sets up event listeners for the complete provider conversation lifecycle.
   *
   * Maps provider-level events to thread-specific UI status updates:
   * - CREATED → "initializing" - Provider is setting up the conversation
   * - IN_PROGRESS → "pending" - Provider is actively generating response
   * - COMPLETED → "success" - Response generation finished successfully
   * - FAILED → "error" - An error occurred during generation
   * - CANCELLED → "stop" - User or system cancelled the request
   */
  private setupConversationEventListeners() {
    emitter.on(
      EventNames.PROVIDER_CONVERSATION_CREATED,
      async ({ threadId }) =>
        await this.handleThreadStreamStatusUpdate(threadId, "initializing"),
    );

    emitter.on(
      EventNames.PROVIDER_CONVERSATION_IN_PROGRESS,
      ({ threadId, delta }) => this.handleStreamProgress(threadId, delta),
    );

    emitter.on(
      EventNames.PROVIDER_CONVERSATION_COMPLETED,
      async ({ threadId }) =>
        await this.handleThreadStreamStatusUpdate(threadId, "success"),
    );

    emitter.on(
      EventNames.PROVIDER_CONVERSATION_FAILED,
      async ({ threadId }) =>
        await this.handleThreadStreamStatusUpdate(threadId, "error"),
    );

    emitter.on(
      EventNames.PROVIDER_CONVERSATION_CANCELLED,
      async ({ threadId }) =>
        await this.handleThreadStreamStatusUpdate(threadId, "stop"),
    );

    emitter.on(
      EventNames.THREAD_DELETED,
      async ({ threadId }) => await this.handleThreadDeleted(threadId),
    );
  }

  /**
   * Creates a new assistant message with proper ordering and default values.
   *
   * This method handles the complex logic of:
   * - Calculating the next orderSeq by counting existing messages
   * - Setting appropriate defaults for assistant-specific fields
   * - Ensuring consistent message state initialization
   *
   * @param message - Partial message data (excludes system-managed fields)
   * @returns The created assistant message with all required fields populated
   */
  async createAssistantMessage(
    message: Omit<
      CreateMessageData,
      | "orderSeq"
      | "role"
      | "status"
      | "tokenCount"
      | "content"
      | "isThinkBlockCollapsed"
      | "hasSequentialContent"
    >,
  ): Promise<Message> {
    try {
      // Calculate next sequence number by counting existing messages in thread
      const existingMessages = await this.messageService._getMessagesByThreadId(
        message.threadId,
      );
      const nextOrderSeq = existingMessages.length + 1;

      // Create assistant message with system-managed defaults
      const newMessage = await this.messageService._insertMessage({
        ...message,
        orderSeq: nextOrderSeq,
        role: "assistant",
        status: "pending", // Will be updated as streaming progresses
        tokenCount: 0, // Will be calculated after completion
        content: "", // Will be populated via streaming
        isThinkBlockCollapsed: false, // Default to expanded for new messages
        hasSequentialContent: false, // Will be set to true if MCP reflection is used
      });

      this.threadIdTolastAssistantMessageMap.set(message.threadId, newMessage);

      return newMessage;
    } catch (error) {
      logger.error("ChatService:createAssistantMessage error", { error });
      throw error;
    }
  }

  async updateMessage(
    messageId: string,
    updateData: UpdateMessageData,
  ): Promise<void> {
    try {
      await this.messageService._updateMessage(messageId, updateData);
    } catch (error) {
      logger.error("ChatService:updateMessage error", { error });
      throw error;
    }
  }

  @ServiceHandler(CommunicationWay.RENDERER_TO_MAIN__TWO_WAY)
  async getMessagesByThreadId(
    _event: Electron.IpcMainEvent,
    threadId: string,
  ): Promise<Message[]> {
    try {
      const messages =
        await this.messageService._getMessagesByThreadId(threadId);
      const lastAssistantMessage =
        this.threadIdTolastAssistantMessageMap.get(threadId);
      if (lastAssistantMessage && messages.length > 0) {
        messages[messages.length - 1].content = lastAssistantMessage.content;
      }
      return messages;
    } catch (error) {
      logger.error("ChatService:getMessagesByThreadId error", { error });
      throw error;
    }
  }
}
